package ro.ubbcluj.cs.damate.plugin.services;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.adaptor.EclipseStarter;
import ro.ubbcluj.cs.damate.plugin.DamateActivator;
import ro.ubbcluj.cs.damate.plugin.amq.PluginEventConsumer;
import ro.ubbcluj.cs.damate.plugin.amq.PluginEventProducer;
import ro.ubbcluj.cs.damate.plugin.events.EventType;
import ro.ubbcluj.cs.damate.plugin.events.PluginEvent;
import ro.ubbcluj.cs.damate.plugin.monitors.EventMonitor;
import ro.ubbcluj.cs.damate.plugin.utils.SpringUtils;

import javax.jms.JMSException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class DamateMonitoringService {

	private static final String MONITORS_EXTENSION_POINT = DamateActivator.PLUGIN_ID + ".monitors"; //$NON-NLS-1$

	private boolean monitoring = false;

	private ListenerList monitors = new ListenerList();

    private AuthenticationService authenticationService;
	private PluginEventConsumer eventConsumer;
	private PluginEventProducer eventProducer;
	
	
	/**
	 * This field maps the symbolic name of bundles to the last loaded version.
	 * This information is handy for filling in missing bundle version information
	 * for singleton bundles.
	 * @see #registerBundleVersion(PluginEvent)
	 */
	private Map<String, String> bundleVersionMap = new HashMap<String, String>();

	public void startMonitoring() {
		if (isMonitoring()) {
			return;
		}
		
		startEventConsumer();
		this.eventProducer = (PluginEventProducer)SpringUtils.getBean("pluginEventProducer");
		startMonitors();
		this.monitoring = true;
	}

	public synchronized void stopMonitoring() {
		if (!isMonitoring()) {
			return;
		}

		stopMonitors();
		stopEventConsumer();
		
		this.eventProducer = null;
		this.monitoring = false;
	}

	public boolean isMonitoring() {
		return monitoring;
	}
	
	protected void startEventConsumer() {
		if (eventConsumer != null) {
			return;
		}		
		waitForWorkbenchToFinishStarting();
		this.eventConsumer = (PluginEventConsumer)SpringUtils.getBean("pluginEventConsumer");
		eventConsumer.start();
	}
	
	protected void waitForWorkbenchToFinishStarting() {
		while (!EclipseStarter.isRunning()) {
			try {				
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				
			}
		}
	}
	
	protected void stopEventConsumer() {
		try	{
			eventConsumer.stop();
		}	catch(JMSException e)	{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.eventConsumer = null;
	}	
	
	public void recordEvent(PluginEvent event,	String bundleId, String bundleVersion) {
		event.setBundleId(bundleId);
		event.setBundleVersion(bundleVersion);
		registerBundleVersion(event);
		if (event.getBundleVersion() == null) {
			event.setBundleVersion(getBundleVersion(event.getBundleId()));
		}
		try	{
				if (getAuthenticationService().isShowLoginDialog()) {
						getAuthenticationService().showLoginDialog();
        }
        eventProducer.sendEvent(event);
		}	catch(JMSException e)	{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

    public AuthenticationService getAuthenticationService() {
        if (authenticationService == null) {
            this.authenticationService = DamateActivator.getDefault().getAuthenticationService();
        }
        return authenticationService;
    }

    /**
	 * If the event represents a bundle activation, record a mapping between the
	 * bundleId and bundleVersion. This information is used to fill in missing
	 * information when an event comes in with just a bundleId and no version
	 * information. This assumes that the bundle is a singleton. That is, there
	 * is no provision here for dealing with multiple versions of bundles. If
	 * the event is a result of something that may come from a non-singleton
	 * bundle, then it is the responsibility of the event source to determine
	 * the appropriate version.
	 * 
	 * @param event instance of {@link PluginEvent}.
	 */
	private void registerBundleVersion(PluginEvent event) {
		/*
		 * This is a bit of a hack since we're using inside knowledge about a
		 * particular type of event (that we're pretty well decoupled
		 * from--though this knowledge does constitute a relatively tight
		 * form of coupling). If the event tells us that a bundle has been
		 * started, we'll move on; otherwise, we bail out. We're not interested
		 * in other bundle events (like stops, etc.), since these
		 * events will be relatively rare for the kinds of bundles we actually
		 * care about. 
		 */
		if (!("bundle".equals(event.getKind()))) return; //$NON-NLS-1$
		if (!("started".equals(event.getWhat()))) return; //$NON-NLS-1$
		
		synchronized (bundleVersionMap) {
			bundleVersionMap.put(event.getBundleId(), event.getBundleVersion());
		}
	}

	private String getBundleVersion(String bundleId) {
		if (bundleId == null) {
			return null;
		}
		synchronized (bundleVersionMap) {
			return bundleVersionMap.get(bundleId);
		}
	}

	protected void startMonitors() {
		IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor(MONITORS_EXTENSION_POINT);
		for (IConfigurationElement element : elements) {
			if ("monitor".equals(element.getName())) {
				try {
					Object monitor = element.createExecutableExtension("class");
					if (monitor instanceof EventMonitor) {
						startMonitor((EventMonitor) monitor);
					}
				} catch (CoreException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private void startMonitor(EventMonitor monitor) {
		monitor.startMonitoring(this);
		monitors.add(monitor);
	}

	protected void stopMonitors() {
		for (Object monitor : monitors.getListeners()) {
			stopMonitor((EventMonitor) monitor);
		}
	}

	private void stopMonitor(EventMonitor monitor) {
		monitor.stopMonitoring();
		monitors.remove(monitor);
	}	
}
